// Copyright 2025 NetApp, Inc. All Rights Reserved.

package common

import (
	"context"
	"fmt"

	hash "github.com/mitchellh/hashstructure/v2"

	"github.com/netapp/trident/config"
	"github.com/netapp/trident/core"
	. "github.com/netapp/trident/logging"
	"github.com/netapp/trident/pkg/collection"
	"github.com/netapp/trident/pkg/convert"
	"github.com/netapp/trident/storage"
	sa "github.com/netapp/trident/storage_attribute"
	storageclass "github.com/netapp/trident/storage_class"
	"github.com/netapp/trident/utils/errors"
)

const (
	autoStorageClassPrefix = "auto_sc_%d"
)

// GetStorageClass accepts a list of volume creation options and returns a
// matching storage class.  If the orchestrator already has a matching
// storage class, that is returned; otherwise a new one is created and
// registered with the orchestrator.
func GetStorageClass(
	ctx context.Context, options map[string]string, o core.Orchestrator,
) (*storageclass.Config, error) {
	Logc(ctx).Trace(">>>> GetStorageClass")
	defer func(ctx context.Context) { Logc(ctx).Trace("<<<< GetStorageClass") }(ctx)

	// Create a storage class based on available options
	newScConfig, err := makeStorageClass(ctx, options)
	if err != nil {
		return nil, err
	}

	// Check existing storage classes for a match based on the name
	sc, err := o.GetStorageClass(ctx, newScConfig.Name)
	if err != nil && !errors.IsNotFoundError(err) {
		return nil, err
	}
	if sc != nil {
		Logc(ctx).WithField("storageClass", sc.Config.Name).Debug("Matched existing storage class.")
		return sc.Config, nil
	}

	// No match found, so register the new storage class
	addedSc, err := o.AddStorageClass(ctx, newScConfig)
	if err != nil {
		Logc(ctx).WithField("storageClass", newScConfig.Name).WithError(err).Error("Could not add the storage class.")
		return nil, err
	}

	return addedSc.Config, nil
}

// MakeStorageClass accepts a list of volume creation options and creates a
// matching storage class.  The name of the new storage class contains a hash
// of the attributes it contains, thereby enabling comparison of storage
// classes generated by this method by simply comparing their names.
func makeStorageClass(ctx context.Context, options map[string]string) (*storageclass.Config, error) {
	Logc(ctx).Trace(">>>> makeStorageClass")
	defer Logc(ctx).Trace("<<<< makeStorageClass")

	scConfig := new(storageclass.Config)

	if p, ok := options[sa.StoragePools]; ok {
		if pools, err := sa.CreateBackendStoragePoolsMapFromEncodedString(p); err != nil {
			return nil, err
		} else {
			scConfig.Pools = pools
			delete(options, sa.StoragePools)
		}
	}

	if p, ok := options[sa.AdditionalStoragePools]; ok {
		if additionalPools, err := sa.CreateBackendStoragePoolsMapFromEncodedString(p); err != nil {
			return nil, err
		} else {
			scConfig.AdditionalPools = additionalPools
			delete(options, sa.AdditionalStoragePools)
		}
	}

	// Map options to storage class attributes
	scConfig.Attributes = make(map[string]sa.Request)
	for k, v := range options {
		// format: attribute: "type:value"
		req, err := sa.CreateAttributeRequestFromAttributeValue(k, v)
		if err != nil {
			Logc(ctx).WithFields(LogFields{
				"storageClass":            scConfig.Name,
				"storageClass_parameters": options,
			}).WithError(err).Debug("Frontend ignoring storage class attribute.")
			continue
		}
		scConfig.Attributes[k] = req
	}

	// Set name based on hash value
	scHash, err := hash.Hash(scConfig, hash.FormatV2, nil)
	if err != nil {
		Logc(ctx).WithFields(LogFields{
			"storageClass":            scConfig.Name,
			"storageClass_parameters": options,
		}).WithError(err).Error("Frontend could not hash the storage class attributes.")
		return nil, err
	}

	scConfig.Name = fmt.Sprintf(autoStorageClassPrefix, scHash)

	return scConfig, nil
}

// GetVolumeConfig accepts a set of parameters describing a volume creation request
// and returns a volume config structure suitable for passing to the orchestrator core.
func GetVolumeConfig(
	ctx context.Context, name, storageClass string, sizeBytes int64, opts map[string]string,
	protocol config.Protocol, accessMode config.AccessMode, volumeMode config.VolumeMode,
	requisiteTopologies, preferredTopologies []map[string]string,
) (*storage.VolumeConfig, error) {
	Logc(ctx).Trace(">>>> GetVolumeConfig")
	defer Logc(ctx).Trace("<<<< GetVolumeConfig")

	// If snapshotDir is provided, ensure it is lower case
	snapshotDir := collection.GetV(opts, "snapshotDir", "")
	if snapshotDir != "" {
		snapDirFormatted, err := convert.ToFormattedBool(snapshotDir)
		if err != nil {
			Logc(ctx).WithError(err).Errorf("Invalid boolean value for snapshotDir: %v.", snapshotDir)
			return nil, err
		}
		snapshotDir = snapDirFormatted
	}

	cfg := &storage.VolumeConfig{
		Name:                name,
		Size:                fmt.Sprintf("%d", sizeBytes),
		StorageClass:        storageClass,
		Protocol:            protocol,
		AccessMode:          accessMode,
		VolumeMode:          volumeMode,
		SpaceReserve:        collection.GetV(opts, "spaceReserve", ""),
		SecurityStyle:       collection.GetV(opts, "securityStyle", ""),
		SplitOnClone:        collection.GetV(opts, "splitOnClone", ""),
		SnapshotPolicy:      collection.GetV(opts, "snapshotPolicy", ""),
		SnapshotReserve:     collection.GetV(opts, "snapshotReserve", ""),
		SnapshotDir:         snapshotDir,
		ExportPolicy:        collection.GetV(opts, "exportPolicy", ""),
		UnixPermissions:     collection.GetV(opts, "unixPermissions", ""),
		BlockSize:           collection.GetV(opts, "blocksize", ""),
		Qos:                 collection.GetV(opts, "qos", ""),
		QosType:             collection.GetV(opts, "type", ""),
		FileSystem:          collection.GetV(opts, "fstype|fileSystemType", ""),
		Encryption:          collection.GetV(opts, "encryption", ""),
		CloneSourceVolume:   collection.GetV(opts, "from", ""),
		CloneSourceSnapshot: collection.GetV(opts, "fromSnap|fromSnapshot", ""),
		ServiceLevel:        collection.GetV(opts, "serviceLevel", ""),
		CVSStorageClass:     collection.GetV(opts, "cvsStorageClass", ""),
		Network:             collection.GetV(opts, "network", ""),
		RequisiteTopologies: requisiteTopologies,
		PreferredTopologies: preferredTopologies,
		Zone:                collection.GetV(opts, "zone", ""),
	}
	Logc(ctx).WithField("VolumeConfig", cfg).Trace("Returning volume config.")

	return cfg, nil
}

func CombineAccessModes(ctx context.Context, accessModes []config.AccessMode) config.AccessMode {
	Logc(ctx).Trace(">>>> CombineAccessModes")
	defer Logc(ctx).Trace("<<<< CombineAccessModes")

	volConfigAccessMode := config.ModeAny
	for _, accessMode := range accessModes {
		// Rules for combining multiple access modes into a single access mode:

		// Current AccessMode       Next AccessMode     Result
		// Any                      Any                 Any
		// Any                      ReadWriteOnce       ReadWriteOnce
		// Any                      ReadOnlyMany        ReadOnlyMany
		// Any                      ReadWriteMany       ReadWriteMany

		// ReadWriteOnce            Any                 ReadWriteOnce
		// ReadWriteOnce            ReadWriteOnce       ReadWriteOnce
		// ReadWriteOnce            ReadOnlyMany        ReadWriteMany
		// ReadWriteOnce            ReadWriteMany       ReadWriteMany

		// ReadWriteOncePod            Any                 ReadWriteOnce
		// ReadWriteOncePod            ReadWriteOnce       ReadWriteOnce
		// ReadWriteOncePod            ReadOnlyMany        ReadWriteMany
		// ReadWriteOncePod            ReadWriteMany       ReadWriteMany

		// ReadOnlyMany             Any                 ReadOnlyMany
		// ReadOnlyMany             ReadWriteOnce       ReadWriteMany
		// ReadOnlyMany             ReadOnlyMany        ReadOnlyMany
		// ReadOnlyMany             ReadWriteMany       ReadWriteMany

		// ReadWriteMany            Any                 ReadWriteMany
		// ReadWriteMany            ReadWriteOnce       ReadWriteMany
		// ReadWriteMany            ReadOnlyMany        ReadWriteMany
		// ReadWriteMany            ReadWriteMany       ReadWriteMany
		if volConfigAccessMode == config.ModeAny {
			volConfigAccessMode = accessMode
		} else if volConfigAccessMode == config.ReadWriteOnce || volConfigAccessMode == config.ReadWriteOncePod {
			if accessMode == config.ReadOnlyMany || accessMode == config.ReadWriteMany {
				volConfigAccessMode = config.ReadWriteMany
			}
		} else if volConfigAccessMode == config.ReadOnlyMany {
			if accessMode == config.ReadWriteOnce || accessMode == config.ReadWriteOncePod || accessMode == config.ReadWriteMany {
				volConfigAccessMode = config.ReadWriteMany
			}
		}
	}

	Logc(ctx).WithField("AccessMode", volConfigAccessMode).Trace("Returning selected access mode.")

	return volConfigAccessMode
}
